
/*    This file is part of the Managarm operating system.
 *   Copyright (C) 2007, 2008, 2009  Alexander van der Grinten
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>. */

package org.managarm.jmanacclib.generic;

// import various java util classes
import java.util.Iterator;
import java.util.ArrayList;
import java.util.HashMap;

//import various virtual architecture classes
import org.managarm.jmanavalib.core.VaProcedure;

public final class GnMthCompiler {
	// holds the code we have to compile
	public final GnMethod method;
	// maps procedure objects to procedure descriptions
	public final HashMap<GnProcedure, GnProcInfo> procedures
			= new HashMap<GnProcedure, GnProcInfo>();
	
	// constructs this compiler object
	public GnMthCompiler(GnMethod method) {
		this.method = method;
	}
	
	// compiles generic code to intermediate code
	public VaProcedure compile() {
		// analyze all procedures
		final GnProcInfo description = analyzeProcedure(method,
				method.codeObject);
		
		// analyze (indirect) frame dependencies
		Iterator<GnProcInfo> iterator = procedures.values().iterator();
		while(iterator.hasNext())
			analyzeProcDependencies(iterator.next());
		
		final GnProcCompiler compiler
				= new GnProcCompiler(this, description, null);
		return compiler.compile();
	}
	
	private final GnProcInfo analyzeProcedure(final GnMethod method,
			final GnProcedure procedure) {
		// XXX: is that really a important requirement
		if(procedures.containsKey(procedure))
			return procedures.get(procedure);
		
		// create a new procedure description
		final GnProcInfo description = new GnProcInfo(method, procedure);
		procedures.put(procedure, description);
		
		// analyze all jump instruction
		for(int i = 0; i < procedure.frames.size(); i++)
			analyzeJumps(description, description.frameInfos[i]);
		
		return description;
	}
	
	// determines all jump instructions to other frames
	private final void analyzeJumps(final GnProcInfo procedure,
			final GnFrameInfo frame) {
		for(int i = 0; i < frame.frameObject.instructions.size(); i++) {
			final GnInst opcode = frame.frameObject.instructions.get(i);
			
			if(opcode instanceof GnInst.InstJump) {
				final GnInst.InstJump inst = (GnInst.InstJump)opcode;
				
				// add a link to the target frame
				final GnFrameInfo target = procedure.frameInfos[inst.frameId];
				target.jumps.add(new GnFrameInfo.JumpInfo
						(GnFrameInfo.JumpInfo.MODE_STANDARD, frame, i));
			}else if(opcode instanceof GnInst.InstCatch) {
				final GnInst.InstCatch inst = (GnInst.InstCatch)opcode;

				// create a new procedure information struct for the try-block
				final GnProcInfo procinfo = analyzeProcedure(null, inst.procedure);
				
				final GnFrameInfo target = procinfo.frameInfos[0];
				target.jumps.add(new GnFrameInfo.JumpInfo
						(GnFrameInfo.JumpInfo.MODE_STANDARD, frame, i));
				analyzeExceptions(procinfo, procedure.frameInfos[inst.frame]);
			}
		}
	}
	
	private final void analyzeExceptions(final GnProcInfo procedure,
			final GnFrameInfo handler) {
		for(int j = 0; j < procedure.frameInfos.length; j++) {
			final GnFrameInfo frame = procedure.frameInfos[j];
			
			for(int i = 0; i < frame.frameObject.instructions.size(); i++) {
				final GnInst opcode = frame.frameObject.instructions.get(i);
				
				if(opcode instanceof GnInst.InstReturn) {
					handler.jumps.add(new GnFrameInfo.JumpInfo
							(GnFrameInfo.JumpInfo.MODE_EXCEPTION, frame, i));
				}else if(opcode instanceof GnInst.InstLeave) {
					handler.jumps.add(new GnFrameInfo.JumpInfo
							(GnFrameInfo.JumpInfo.MODE_EXCEPTION, frame, i));
				}else if(opcode instanceof GnInst.InstThrow) {
					handler.jumps.add(new GnFrameInfo.JumpInfo
							(GnFrameInfo.JumpInfo.MODE_EXCEPTION, frame, i));
				}// TODO: implement method that don't return
			}
		}
	}
	
	// analyze the dependencies of all frame of "procedure"
	private final void analyzeProcDependencies(final GnProcInfo procedure) {
		for(int i = 0; i < procedure.codeObject.frames.size(); i++)
			analyzeFrameDependencies(procedure.frameInfos[i]);
	}
	
	// add all dependencies of "info" to all frames that jump to "info"
	private final void analyzeFrameDependencies(final GnFrameInfo info) {
		// note that we have to clone the argument set here as it
		// can be modified by the inner loop.
		final ArrayList<Integer> arguments = new ArrayList<Integer>();
		arguments.addAll(info.argList);
		for(int j = 0; j < arguments.size(); j++) {
			final int register = arguments.get(j);
			
			// add the argument to all frames that jump to "info"
			for(int k = 0; k < info.jumps.size(); k++) {
				final GnFrameInfo.JumpInfo jump = info.jumps.get(k);
				if(jump.jumpMode == GnFrameInfo.JumpInfo.MODE_STANDARD) {
					jump.sourceFrame.addDependence(register, jump.sourceIp);
				}else if(jump.jumpMode == GnFrameInfo.JumpInfo.MODE_EXCEPTION) {
					if(register != info.frameObject.exception)
						jump.sourceFrame.addDependence(register, jump.sourceIp);
				}else{
					throw new UnsupportedOperationException();
				}
			}
		}
	}
}
